// Copyright (c) 2012 GCT Semiconductor, Inc. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <fcntl.h>
#include <assert.h>
#include <errno.h>
#include <net/if.h>
#include <sys/ioctl.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <dirent.h>
#include <stdarg.h>
#include <wchar.h>
#include <linux/version.h>
#include <signal.h>

#include "cm.h"
#include "exitcb.h"
#include "profilestring.h"
#include "dhclient.h"

extern void reg_indications(APIHAND *api_handle);
extern void kill_dhclient(int dev_idx);
extern int setup_device_conf(GDEV_ID *ID, const unsigned char *mac);
extern void load_device_conf(dev_conf_t *conf, const char *mac);
extern dev_conf_t default_dev_conf;

int cm_init(int read_only);
int cm_deinit(void);

APIHAND cm_api_handle;
char cm_odev_list[MAX_DEV];
dev_conf_t cm_dev_conf[MAX_DEV];
int cm_odev_cnt;
cm_common_conf_t cm_common_conf;


int get_first_odev(void)
{
	int i;

	if (cm_odev_cnt) {
		for (i = 0; i < MAX_DEV; i++) {
			if (cm_odev_list[i])
				return i;
		}
	}
	return 0;
}

void add_odev_list(int dev_idx)
{
	assert(!cm_odev_list[dev_idx]);
	cm_odev_list[dev_idx] = 1;
	cm_odev_cnt++;
}


int del_odev_list(int dev_idx)
{
	if (cm_odev_list[dev_idx]) {
		cm_odev_list[dev_idx] = 0;
		cm_odev_cnt--;
		return 1;
	}
	return 0;
}

int cm_get_filesize(const char *file)
{
	int fd, len;

	if ((fd = open(file, O_RDONLY)) < 0) {
		cm_printf("open(%s) fail. %s(%d)\n", file, strerror(errno), errno);
		return -1;
	}

	len = lseek(fd, 0, SEEK_END);
	close(fd);
	return len;
}

int cm_read_file(const char *file, char *buf, int buf_size)
{
	int fd, len, total = 0;

	if ((fd = open(file, O_RDONLY)) < 0) {
		cm_printf("open(%s) fail. %s(%d)\n", file, strerror(errno), errno);
		return -1;
	}
	while ((len = read(fd, &buf[total], buf_size-total)) > 0) {
		total += len;
		if (buf_size-total == 0)
			break;
	}

	if (len < 0)
		cm_printf("read(%s) fail. %s(%d)\n", file, strerror(errno), errno);

	close(fd);
	return total;
}

int cm_write_file(const char *file, char *buf, int buf_size, int flags)
{
	int fd, len = 0, total = 0;
	mode_t mode = 0;

	if (flags & O_CREAT)
		mode = 0644;

	if ((fd = open(file, flags, mode)) < 0) {
		cm_printf("open(%s) fail. %s(%d)\n", file, strerror(errno), errno);
		return -1;
	}

	if (buf_size) {
		while ((len = write(fd, &buf[total], buf_size-total)) > 0) {
			total += len;
			if (buf_size-total == 0)
				break;
		}
	}

	if (len < 0)
		cm_printf("write(%s) fail\n", file);

	close(fd);
	return total;
}

int cm_cmp_file(const char *file, const char *file2)
{
	int fd = -1, fd2 = -1;
	int ret = -1;
	int len, len2;
	char buf[4096], buf2[4096];
	
	if ((fd = open(file, O_RDONLY)) < 0) {
		cm_printf("open(%s) fail. %s(%d)\n", file, strerror(errno), errno);
		goto out;
	}
	if ((fd2 = open(file2, O_RDONLY)) < 0) {
		cm_printf("open(%s) fail. %s(%d)\n", file2, strerror(errno), errno);
		goto out;
	}

	do {
		if ((len = read(fd, buf, sizeof(buf))) < 0) {
			cm_printf("read(%d:%s) fail. %s(%d)\n", fd, file, strerror(errno), errno);
			break;
		}
		if ((len2 = read(fd2, buf2, sizeof(buf2))) < 0) {
			cm_printf("read(%d:%s) fail. %s(%d)\n", fd2, file2, strerror(errno), errno);
			break;
		}

		if (len != len2)
			break;
		if (len < 0 || len2 < 0)
			break;
			
		if (len == 0 && len2 == 0) {
			ret = 0;
			break;
		}

		if (memcmp(buf, buf2, len))
			break;
	} while (1);

out:
	if (fd > 0)
		close(fd);
	if (fd2 > 0)
		close(fd2);
	return ret;
}

int cm_get_dir_entry(const char *dir, int index, char *filepath, int size)
{
	DIR *dirp;
	struct dirent *dp;
	int idx = 0, ret = -1;
	char *file;

	dirp = opendir(dir);

	while ((dp = readdir(dirp)) != NULL) {
		file = dp->d_name;
		if (!strcmp(file, ".") || !strcmp(file, ".."))
			continue;

		if (idx++ == index) {
			strncpy(filepath, file, size);
			ret = 0;
			break;
		}
	}

	closedir(dirp);
	return ret;
}

int cm_load_file_lines(const char *file, int lines, ...)
{
	char line[1024];
	FILE *fp;
	va_list list;
	char *p;
	int i, len;

	if (!(fp = fopen(file, "rt"))) {
		cm_eprintf("fopen(%s) error\n", file);
		return -1;
	}

	va_start(list, lines);
	for (i = 0; i < lines; i++) {
		p = va_arg(list, char *);
		if (p) *p = 0;
		if (!fgets(line, sizeof(line), fp))
			break;
		len = strlen(line);
		if (len >= 2 && line[len-2] == '\r') line[len-2] = 0;
		if (len >= 1 && line[len-1] == '\n') line[len-1] = 0;
		//cm_printf("[%s]\n", line);
		if (p) strcpy(p, line);
	}
	va_end(list);

	fclose(fp);
	return i;
}

int cm_store_file_lines(const char *file, int lines, ...)
{
	FILE *fp;
	va_list list;
	char *p;
	int i;

	if (!(fp = fopen(file, "wt"))) {
		cm_eprintf("fopen(%s) error\n", file);
		return -1;
	}

	va_start(list, lines);
	for (i = 0; i < lines; i++) {
		p = va_arg(list, char *);
		if (fprintf(fp, "%s\n", p ? p : "") < 0) {
			cm_eprintf("fprintf(%s) error\n", file);
			break;
		}
		//cm_printf("[%s]\n", p);
	}
	va_end(list);

	fclose(fp);
	return i;
}

char *cm_get_dev_idx2name(int dev_idx)
{
	static char name_buf[MAX_DEV][8];
	char *name = name_buf[dev_idx];

	sprintf(name, GCT_WM_PREFIX "%d", dev_idx-1);
	return name;
}

int cm_get_dhcp_ip(int dev_idx, char *ip)
{
#define sizeof_sa_sin_port	2
#define p_inaddrr(x) (&ifr.x[sizeof_sa_sin_port])
	struct ifreq ifr;
	struct in_addr inaddr;
	int fd = 0;
	int ret = 0;

	fd = socket(PF_PACKET, SOCK_DGRAM, IPPROTO_IP);
	if (fd < 0) {
		cm_eprintf("socket\n");
		return -1;
	}

	memset(&ifr, 0, sizeof(ifr));
	strncpy(ifr.ifr_name, cm_get_dev_idx2name(dev_idx), IFNAMSIZ);
	if (ioctl(fd, SIOCGIFADDR, &ifr) < 0) {
		//cm_dprintf("ioctl SIOCGIFFLAGS(%d)\n", fd);
		ret = -1;
		goto out;
	}
	memcpy(&inaddr, p_inaddrr(ifr_addr.sa_data), sizeof(inaddr));
	if (ip)
		strcpy(ip, inet_ntoa(inaddr));
out:
	if (fd > 0)
		close(fd);
	return ret;
}

static void extract_fw_version(const char *fw_ver, unsigned *app_ver, unsigned *mac_ver)
{
	const char *fw_ver_fmt = "FW(%d.%d.%d.%d : %d.%d.%d.%d)";
	int v[8];

	sscanf(fw_ver, fw_ver_fmt, &v[0], &v[1], &v[2], &v[3], &v[4], &v[5], &v[6], &v[7]);

	*app_ver = ((char)v[0] << 24) | ((char)v[1] << 16) | ((char)v[2] << 8) | (char)v[3];
	*mac_ver = ((char)v[4] << 24) | ((char)v[5] << 16) | ((char)v[6] << 8) | (char)v[7];
}

static void extract_fw_w_version(const wchar_t *fw_ver, unsigned *app_ver, unsigned *mac_ver)
{
	char ch_fw_ver[128];
	
	wcstombs((char *)ch_fw_ver, fw_ver, wcslen(fw_ver)+1);

	extract_fw_version(ch_fw_ver, app_ver, mac_ver);
}

static void setup_fw_version(int dev_idx, wchar_t *fw_ver)
{
	dev_conf_t *conf = &cm_dev_conf[dev_idx];

	extract_fw_w_version(fw_ver, &conf->fw_app_version, &conf->fw_mac_version);

	#if 0
	cm_dprintf("fw_app_version=0x%08x\n", conf->fw_app_version);
	cm_dprintf("fw_mac_version=0x%08x\n", conf->fw_mac_version);
	#endif
}

static int send_print_log_on_eap(GDEV_ID_P pID)
{
	#define STR_LOG_ON_EAP "log on eap\n"
	
	char *buf = STR_LOG_ON_EAP;
	int len = strlen(STR_LOG_ON_EAP);
	char hci_buf[HCI_HEADER_SIZE + 32];
	hci_t *hci = (hci_t *) hci_buf;

	hci->cmd_evt = _H2B(WIMAX_CLI_CMD);
	hci->length = _H2B(len);
	memcpy(hci->data, buf, len);

	len += HCI_HEADER_SIZE;

	if (GAPI_WriteHCIPacket(pID, hci_buf, len) != GCT_API_RET_SUCCESS) {
		cm_printf("Write HCI failure\n");
		return -1;
	}

	return 0;

}

int device_open(GDEV_ID_P pID)
{
	cm_common_conf_t *pconf = &cm_common_conf;
	WIMAX_API_DEVICE_INFO info;
	char bl_ver[64];

	if (GAPI_WiMaxDeviceOpen(pID) == GCT_API_RET_SUCCESS) {
		add_odev_list(pID->deviceIndex);
		GAPI_GetBootloaderVersion(pID, bl_ver, sizeof(bl_ver));
		cm_printf("BL version: %s\n", bl_ver);

		if (GAPI_GetDeviceInformation(pID, &info) != GCT_API_RET_SUCCESS)
			return -1;

		setup_fw_version(pID->deviceIndex, (wchar_t *)info.swVersion.version);

		cm_printf("HW version: %S\n", (wchar_t *)info.hwVersion.version);
		cm_printf("SW version: %S\n", (wchar_t *)info.swVersion.version);
		
		setup_device_conf(pID, info.macAddress);

		if (pconf->eap_log_enable && CM_USE_EEAP && CAP_EEAP_ENABLED(pID->deviceIndex))
			send_print_log_on_eap(pID);

		if (pconf->api_mode != GCT_WIMAX_API_PRIVILEGE_READ_ONLY) {
			if (pconf->auto_connect_enable)
				cm_request_auto_connection(pID->deviceIndex);
			#if defined(CONFIG_DM_INTERFACE)
			else
				cm_profiling_rf_up(pID->deviceIndex);
			#endif
		}
		return 0;
	}
	else {
		cm_printf("open fail device[%d]\n", pID->deviceIndex);
		return -1;
	}
}


int device_close(GDEV_ID_P pID)
{
	cm_common_conf_t *pconf = &cm_common_conf;
	
	if (del_odev_list(pID->deviceIndex)) {
		if (pconf->api_mode != GCT_WIMAX_API_PRIVILEGE_READ_ONLY)
			dh_stop_dhclient(pID->deviceIndex);

		if (GAPI_WiMaxDeviceClose(pID) == GCT_API_RET_SUCCESS)
			return 0;
		else {
			cm_printf("close fail device[%d]\n", pID->deviceIndex);
			return -1;
		}
	}
	cm_printf("Not found device[%d]\n", pID->deviceIndex);
	return -1;
}


int open_dev_list(void)
{
	WIMAX_API_HW_DEVICE_ID list[256];
	UINT32 cnt = 256;
	GDEV_ID ID;
	int i;

	ID.apiHandle = cm_api_handle;

	if (GAPI_GetListDevice(cm_api_handle, list, &cnt) == GCT_API_RET_SUCCESS) {
		for (i = 0; i < cnt; i++) {
			ID.deviceIndex = list[i].deviceIndex;
			if (device_open(&ID) < 0)
				return -1;
		}
		return 0;
	}
	else
		return -1;
}


void close_dev_list(void)
{
	GDEV_ID ID;
	int i;

	ID.apiHandle = cm_api_handle;

	for (i = 0; i < sizeof(cm_odev_list)/sizeof(cm_odev_list[0]); i++) {
		if (cm_odev_list[i]) {
			ID.deviceIndex = i;
			device_close(&ID);
		}
			
	}
}

static void cb_cm_deinit(int signal/*, void *info*/)
{
	cm_deinit();
}

#define KEY_log_path							"log_path"
#define KEY_log_level							"log_level"
#define KEY_eap_log_enable						"eap_log_enable"
#define KEY_embedded_eap_enable					"embedded_eap_enable"
#define KEY_oma_dm_enable						"oma_dm_enable"
#define KEY_nonvolatile_dir						"nonvolatile_dir"
#define KEY_run_script_file						"run_script_file"
#define KEY_auto_connect_enable					"auto_connect_enable"
#define KEY_auto_connect_retry_count			"auto_connect_retry_count"
#define KEY_auto_select_profile_index			"auto_select_profile_index"
#define KEY_unknown_net_auto_connect_enable		"unknown_net_auto_connect_enable"
#define KEY_ip_allocation_timeout_sec			"ip_allocation_timeout_sec"
#define KEY_disconnct_on_ip_failure				"disconnct_on_ip_failure"

static void cm_load_common(cm_common_conf_t *pconf)
{
	char *section = "common";
	char str[256], *p;
	int len;

	p = pconf->log_path;
	len = sizeof(pconf->log_path);
	get_profile_string(section, KEY_log_path, "./sdklog", p, len, CONF_FILE);

	get_profile_string(section, KEY_eap_log_enable, "n", str, sizeof(str), CONF_FILE);
	if (!strcasecmp(str, "y"))
		pconf->eap_log_enable = 1;
	else
		pconf->eap_log_enable = 0;

	get_profile_string(section, KEY_embedded_eap_enable, "n", str, sizeof(str), CONF_FILE);
	if (!strcasecmp(str, "y"))
		pconf->embedded_eap_enable = 1;
	else
		pconf->embedded_eap_enable = 0;

	pconf->oma_dm_enable = 0;
	
	p = pconf->nonvolatile_dir;
	len = sizeof(pconf->nonvolatile_dir);
	get_profile_string(section, KEY_nonvolatile_dir, "./", p, len, CONF_FILE);
	len = strlen(pconf->nonvolatile_dir);
	if (pconf->nonvolatile_dir[len-1] == '/' || pconf->nonvolatile_dir[len-1] == '\\')
		pconf->nonvolatile_dir[len-1] = 0;
	
	p = pconf->run_script_file;
	len = sizeof(pconf->run_script_file);
	get_profile_string(section, KEY_run_script_file, "", p, len, CONF_FILE);
	len = strlen(pconf->run_script_file);
	if (pconf->run_script_file[len-1] == '/' || pconf->run_script_file[len-1] == '\\')
		pconf->run_script_file[len-1] = 0;
	
	get_profile_string(section, KEY_log_level, "1", str, sizeof(str), CONF_FILE);
	pconf->log_level = strtoul(str, NULL, 0);
	
	get_profile_string(section, KEY_auto_connect_enable, "n", str, sizeof(str), CONF_FILE);
	if (!strcasecmp(str, "y"))
		pconf->auto_connect_enable = 1;
	else
		pconf->auto_connect_enable = 0;
	
	get_profile_string(section, KEY_auto_connect_retry_count, "0", str, sizeof(str), CONF_FILE);
	pconf->auto_connect_retry_count = strtoul(str, NULL, 0);
	
	get_profile_string(section, KEY_auto_select_profile_index, "0", str, sizeof(str), CONF_FILE);
	pconf->auto_select_profile_index = strtoul(str, NULL, 0);
	
	get_profile_string(section, KEY_unknown_net_auto_connect_enable, "n", str, sizeof(str), CONF_FILE);
	if (!strcasecmp(str, "y"))
		pconf->unknown_net_auto_connect_enable = 1;
	else
		pconf->unknown_net_auto_connect_enable = 0;

	get_profile_string(section, KEY_ip_allocation_timeout_sec, "30", str, sizeof(str), CONF_FILE);
	pconf->ip_allocation_timeout_sec = strtoul(str, NULL, 0);

	get_profile_string(section, KEY_disconnct_on_ip_failure, "n", str, sizeof(str), CONF_FILE);
	if (!strcasecmp(str, "y"))
		pconf->disconnct_on_ip_failure = 1;
	else
		pconf->disconnct_on_ip_failure = 0;
}

static void *cm_auto_connect_thread(void *data)
{
	cm_common_conf_t *pconf = &cm_common_conf;
	cm_msg_cb_t *msg_cb;
	int dev_idx;

	msg_cb = &pconf->auto_conn_msg;
	cm_msg_init(msg_cb);

	cm_printf("Auto connection is ready!\n");
	while (1) {
		dev_idx = DEFAULT_DEVICE;
		if (cm_msg_recv(msg_cb, &dev_idx, NULL) < 0) {
			cm_eprintf("auto connector cm_msg_recv error\n");
			break;
		}
		if (pconf->auto_connect_enable)
			cm_auto_connect(dev_idx);
	}
	
	cm_msg_deinit(msg_cb);
	pconf->auto_conn_thr = (pthread_t) NULL;
	cm_printf("Auto connection finished!\n");
	return NULL;
}

void cm_request_auto_connection(int dev_idx)
{
	cm_common_conf_t *pconf = &cm_common_conf;
	dev_conf_t *conf = &cm_dev_conf[dev_idx];

	if (pconf->api_mode == GCT_WIMAX_API_PRIVILEGE_READ_ONLY)
		return;

	conf->auto_conn_retry_cnt = 0;
	cm_printf("Request auto connection device(%d)\n", dev_idx);
	if (dev_idx > 0)
		cm_msg_send(&pconf->auto_conn_msg, dev_idx, NULL);
}

void cm_retry_auto_connection(int dev_idx)
{
	cm_common_conf_t *pconf = &cm_common_conf;
	dev_conf_t *conf = &cm_dev_conf[dev_idx];

	if (pconf->api_mode == GCT_WIMAX_API_PRIVILEGE_READ_ONLY)
		return;

	if (++conf->auto_conn_retry_cnt == pconf->auto_connect_retry_count) {
		cm_printf("Too many retries(%u), auto_connect_retry_count in cm.conf is %u\n",
			conf->auto_conn_retry_cnt, pconf->auto_connect_retry_count);
	}
	else if (conf->auto_conn_retry_cnt < pconf->auto_connect_retry_count) {
		cm_printf("Re-request(%u/%u) auto connection device(%d)\n",
			conf->auto_conn_retry_cnt, pconf->auto_connect_retry_count, dev_idx);
		if (dev_idx > 0)
			cm_msg_send(&pconf->auto_conn_msg, dev_idx, NULL);
	}
}

static void cm_create_auto_connector(void)
{
	cm_common_conf_t *pconf = &cm_common_conf;

	if (!pconf->auto_connect_enable)
		cm_printf("Auto connection flag was disabled!\n");
	if (pconf->auto_conn_thr) {
		cm_eprintf("Auto connector has been started already!\n");
		return;
	}
	pthread_create(&pconf->auto_conn_thr, NULL, cm_auto_connect_thread, NULL);
}

static void cm_delete_auto_connector(void)
{
	cm_common_conf_t *pconf = &cm_common_conf;
	pthread_t thread;

	if ((thread = pconf->auto_conn_thr)) {
		pconf->auto_conn_thr = (pthread_t) NULL;
		pthread_cancel(thread);
		pthread_join(thread, NULL);
		cm_printf("Auto connector deleted!\n");
	}
	pconf->auto_connect_enable = 0;
}

int cm_init(int read_only)
{
	cm_common_conf_t *pconf = &cm_common_conf;
	GCT_WIMAX_SDK_MODE sdk_mode = 0;
	GCT_WIMAX_API_PARAM param;
	GCT_API_RET ret;

	if (read_only) {
		pconf->api_mode = GCT_WIMAX_API_PRIVILEGE_READ_ONLY;
		pconf->eap_log_enable = 0;
	}
	else
		pconf->api_mode = GCT_WIMAX_API_OPEN_MODE_NORMAL;

	cm_timer_module_init();

	cm_load_common(pconf);

	if (!read_only) {
		cm_create_auto_connector();
		dh_create_dhclient();
	}

	load_device_conf(&default_dev_conf, NULL);

	#if defined(CONFIG_DM_INTERFACE)
	if (!read_only) {
		if (dmif_init() < 0)
			return -1;
		pconf->dm_interface_enable = 1;
	}
	#endif

	if (mkdir(pconf->log_path, 0644) < 0 && errno != EEXIST) {
		cm_eprintf("Access directory(%s) failed %s(%d)\n",
			pconf->log_path, strerror(errno), errno);
		return -1;
	}

	if (pconf->embedded_eap_enable)
		sdk_mode |= GCT_WIMAX_SDK_EMBEDDED_EAP_ENABLED;
	if (pconf->oma_dm_enable)
		sdk_mode |= GCT_WIMAX_SDK_OMA_DM_ENABLED;

	strcpy(param.nonvolatile_dir, pconf->nonvolatile_dir);
	strcpy(param.log_path, pconf->log_path);
	param.log_level = pconf->log_level;
	ret = GAPI_Initialize(sdk_mode, &param);
	if (ret != GCT_API_RET_SUCCESS) {
		cm_eprintf("GAPI_Initialize failed(%d)\n", ret);
		return -1;
	}

	register_exit_cb(cb_cm_deinit);

	ret = GAPI_WiMaxAPIOpen(&cm_api_handle, pconf->api_mode);
	if (ret != GCT_API_RET_SUCCESS) {
		cm_eprintf("GAPI_WiMaxAPIOpen failed(%d)\n", ret);
		return -1;
	}

	reg_indications(cm_api_handle);

	if (open_dev_list() < 0)
		return -1;
	return 0;
}

int cm_deinit(void)
{
	APIHAND apihand = cm_api_handle;
	GCT_API_RET ret;

	cm_delete_auto_connector();

	unregister_exit_cb(cb_cm_deinit);

	close_dev_list();

	dh_delete_dhclient();

	#if (CONFIG_LOG_FILE_BUF_SIZE > 0)
	GAPI_SetDebugLevel(apihand, GAPI_LOG_FLUSH_LEVEL, NULL);
	#endif

	cm_api_handle = NULL;
	GAPI_WiMaxAPIClose(apihand);

	ret = GAPI_DeInitialize();
	if (ret != GCT_API_RET_SUCCESS)
		return -1;

	cm_timer_module_deinit();
	return 0;
}
